/*
 * Copyright (c) 2018 David Boissier.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.codinjutsu.tools.mongo.view.model;

import com.mongodb.DBRef;
import org.bson.Document;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoKeyValueDescriptor;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoNodeDescriptor;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoResultDescriptor;
import org.codinjutsu.tools.mongo.view.nodedescriptor.MongoValueDescriptor;

import javax.swing.tree.TreeNode;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

public class JsonTreeUtils {

    public static TreeNode buildJsonTree(String collectionName, List<Document> documents, int startIndex) {
        JsonTreeNode rootNode = new JsonTreeNode(new MongoResultDescriptor(collectionName));

        int i = startIndex;
        for (Document document : documents) {
            JsonTreeNode currentNode = new JsonTreeNode(MongoValueDescriptor.createDescriptor(i++, document));
            processDocument(currentNode, document);
            rootNode.add(currentNode);
        }
        return rootNode;
    }

    public static TreeNode buildJsonTree(Document document) {
        JsonTreeNode rootNode = new JsonTreeNode(new MongoResultDescriptor());//TODO crappy
        if (document != null) {
            processDocument(rootNode, document);
        }
        return rootNode;
    }

    public static void processDocument(JsonTreeNode parentNode, Document document) {
        for (String key : document.keySet()) {
            Object value = document.get(key);
            JsonTreeNode currentNode = new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor(key, value));
            processValue(value, currentNode);
            parentNode.add(currentNode);
        }
    }

    public static void processObjectList(JsonTreeNode parentNode, List objectList) {
        for (int i = 0; i < objectList.size(); i++) {
            Object value = objectList.get(i);
            JsonTreeNode currentNode = new JsonTreeNode(MongoValueDescriptor.createDescriptor(i, value));
            processValue(value, currentNode);
            parentNode.add(currentNode);
        }
    }

    private static void processValue(Object value, JsonTreeNode currentNode) {
        if (value instanceof Document) {
            processDocument(currentNode, (Document) value);
        } else if (value instanceof DBRef) {
            processDBRef(currentNode, (DBRef) value);
        } else if (value instanceof List) {
            processObjectList(currentNode, (List) value);
        }
    }

    private static void processDBRef(JsonTreeNode parentNode, DBRef dbRef) {
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$ref", dbRef.getCollectionName())));
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$id", dbRef.getId())));
        parentNode.add(new JsonTreeNode(MongoKeyValueDescriptor.createDescriptor("$db", dbRef.getDatabaseName())));
    }

    public static Document buildDocumentObject(JsonTreeNode rootNode) {
        Document document = new Document();
        Enumeration children = rootNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoKeyValueDescriptor descriptor = (MongoKeyValueDescriptor) node.getDescriptor();
            Object value = descriptor.getValue();
            if (value instanceof Document) {
                document.put(descriptor.getKey(), buildDocumentObject(node));
            } else if (value instanceof DBRef) {
                document.put(descriptor.getKey(), buildDBRefObject(node));
            } else if (value instanceof List) {
                document.put(descriptor.getKey(), buildObjectList(node));
            } else {
                document.put(descriptor.getKey(), value);
            }
        }

        return document;
    }

    private static DBRef buildDBRefObject(JsonTreeNode parentNode) {
        Object _id = null;
        String collectionName = null;
        String databaseName = null;
        Enumeration children = parentNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoNodeDescriptor descriptor = node.getDescriptor();
            String formattedKey = descriptor.getKey();
            switch (formattedKey) {
                case "$id":
                    _id = descriptor.getValue();
                    break;
                case "$ref":
                    collectionName = (String) descriptor.getValue();
                    break;
                case "$db":
                    databaseName = (String) descriptor.getValue();
                    break;
                default:
                    throw new IllegalArgumentException("Unexpected key: " + formattedKey);
            }
        }
        if (collectionName == null || _id == null) {
            throw new IllegalArgumentException("When using DBRef, $ref and $id should be set.");
        }
        return new DBRef(databaseName, collectionName, _id);
    }

    private static List buildObjectList(JsonTreeNode parentNode) {
        List<Object> basicDBList = new ArrayList<>();
        Enumeration children = parentNode.children();
        while (children.hasMoreElements()) {
            JsonTreeNode node = (JsonTreeNode) children.nextElement();
            MongoValueDescriptor descriptor = (MongoValueDescriptor) node.getDescriptor();
            Object value = descriptor.getValue();
            if (value instanceof Document) {
                basicDBList.add(buildDocumentObject(node));
            } else if (value instanceof List) {
                basicDBList.add(buildObjectList(node));
            } else {
                basicDBList.add(value);
            }
        }
        return basicDBList;
    }
}
